﻿using System.Diagnostics;
using HashFoo.Core;
using HashFoo.Rest.Common;
using HashFoo.Rest.CommonTests.MockResources;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace HashFoo.Rest.CommonTests
{
    [TestClass]
    public class ClientContextTests
    {
        static readonly ResourceUriCollection DefaultResourceUris =
            new ResourceUriCollection(new[]
            {
                new ResourceUri { ResourceType = typeof(EmptyCommand), Uri = "/mock/uri/doesnt/matter" },
                new ResourceUri { ResourceType = typeof(ResultCommand), Uri = "/mock2" },
                new ResourceUri { ResourceType = typeof(QueryMock), Uri="/querymock1" }
            });

        /* 
         * Test successful client executions -- expect that the completedCallbacks are executed.
         */

        [TestMethod]
        [Description("Tests that the client executes the completed callback if the web request is successful")]
        public void callback_on_successful_empty_command()
        {
            var webClient = new WebClientMock().UseCompletedCallback();
            var serializer = new ResourceSerializerMock();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new EmptyCommand();

            var completed = false;
            TestUtil.WaitFor(syncLock => client.ExecuteCommand(
                commandToExecute,
                () =>
                {
                    completed = true;
                    syncLock.Set();
                }));

            Assert.IsTrue(completed);
        }

        [TestMethod]
        [Description("Tests that a command with a result model returns the result model")]
        public void successful_model_command_callbacks_with_model()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new ResultCommand();

            string commandResult = null;
            const string expectedResult = ResourceSerializerMock.DefaultStringResultModel;
            TestUtil.WaitFor(syncLock => client.ExecuteCommand(
                commandToExecute,
                result =>
                {
                    commandResult = result;
                    syncLock.Set();
                }));

            Assert.IsNotNull(commandResult);
            Assert.AreEqual(expectedResult, commandResult);
        }

        [TestMethod]
        [Description("Tests that a successful query will execute its callback")]
        public void successful_query_executes_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();

            string queryResult = null;
            const string expectedResult = ResourceSerializerMock.DefaultStringResultModel;
            TestUtil.WaitFor(syncLock => client.Query(
                queryToExecute,
                result =>
                {
                    queryResult = result;
                    syncLock.Set();
                }));

            Assert.IsNotNull(queryResult);
            Assert.AreEqual(expectedResult, queryResult);
        }

        [TestMethod]
        [Description("Tests that a successful query with meta data will execute its callback")]
        public void successful_query_with_meta_executes_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();
            var queryMeta = new QueryMeta { Skip = 10, Take = 4 };

            string queryResult = null;
            const string expectedResult = ResourceSerializerMock.DefaultStringResultModel;
            TestUtil.WaitFor(syncLock => client.Query(
                queryToExecute,
                queryMeta,
                result =>
                {
                    queryResult = result;
                    syncLock.Set();
                }));

            Assert.IsNotNull(queryResult);
            Assert.AreEqual(expectedResult, queryResult);
        }


        /*
         * Test failed client executions -- expect that the errorCallback is executed.
         */

        [TestMethod]
        [Description("Tests that an empty model command that results in an error calls the error callback")]
        public void failed_empty_command_calls_errorcallback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new EmptyCommand();

            WebRequestError errorResult = null;
            TestUtil.WaitFor(syncLock => client.ExecuteCommand(
                commandToExecute,
                errorCallback: err =>
                {
                    errorResult = err;
                    syncLock.Set();
                }));

            Assert.IsNotNull(errorResult);
        }

        [TestMethod]
        [Description("Tests that a command that results in an error calls the error callback")]
        public void failed_model_command_calls_errorcallback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new ResultCommand();

            WebRequestError errorResult = null;
            TestUtil.WaitFor(syncLock => client.ExecuteCommand(
                commandToExecute,
                errorCallback: err =>
                {
                    errorResult = err;
                    syncLock.Set();
                }));

            Assert.IsNotNull(errorResult);
        }

        [TestMethod]
        [Description("Tests that a failed query will execute the error callback")]
        public void failed_query_executes_error_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();

            WebRequestError errorResult = null;
            TestUtil.WaitFor(syncLock => client.Query(
                queryToExecute,
                // do nothing on success, the test will timeout with a fail.
                result => Debug.WriteLine("WARNING: completed callback executed, expected error callback"),
                err =>
                {
                    errorResult = err;
                    syncLock.Set();
                }));

            Assert.IsNotNull(errorResult);
        }

        [TestMethod]
        [Description("Tests that a failed query with meta will execute the error callback")]
        public void failed_query_with_meta_executes_error_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();
            var meta = new QueryMeta { Skip = 1 };

            WebRequestError errorResult = null;
            TestUtil.WaitFor(syncLock => client.Query(
                queryToExecute,
                meta,
                // do nothing on success, the test will timeout with a fail.
                result => Debug.WriteLine("WARNING: completed callback executed, expected error callback"),
                err =>
                {
                    errorResult = err;
                    syncLock.Set();
                }));

            Assert.IsNotNull(errorResult);
        }

        /*
         * Test canceled executions -- the cancelCallback should be executed.
         */

        [TestMethod]
        public void canceled_empty_command_calls_cancelcallback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new EmptyCommand();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.ExecuteCommand(
                    commandToExecute,
                    canceledCallback:() => syncLock.Set());

                op.Cancel();
            });

            // assert pass if lock is set.
        }

        [TestMethod]
        public void canceled_model_command_calls_cancelcallback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var commandToExecute = new ResultCommand();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.ExecuteCommand(
                    commandToExecute,
                    canceledCallback: () => syncLock.Set());

                op.Cancel();
            });

            // assert pass if lock is set.
        }

        [TestMethod]
        public void canceled_query_executes_cancel_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.Query(
                    queryToExecute,
                    result => Debug.WriteLine("WARNING: completed callback executed, expected error callback"),
                    canceledCallback: () => syncLock.Set());

                op.Cancel();
            });

            // assert pass if lock is set.
        }

        [TestMethod]
        public void canceled_query_with_meta_executes_cancel_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();
            var meta = new QueryMeta { Skip = 1 };

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.Query(
                    queryToExecute,
                    meta,
                    result => Debug.WriteLine("WARNING: completed callback executed, expected error callback"),
                    canceledCallback: () => syncLock.Set());

                op.Cancel();
            });

            // assert pass if lock is set.
        }

        [TestMethod]
        public void command_with_empty_callback_does_not_invoke_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new EmptyCommand();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.ExecuteCommand(queryToExecute);
                op.Completed += () => syncLock.Set();
            });
        }

        [TestMethod]
        public void command_with_empty_error_callback_does_not_invoke_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new EmptyCommand();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.ExecuteCommand(queryToExecute);
                op.Completed += () => syncLock.Set();
            });
        }

        [TestMethod]
        public void command_with_empty_cancel_callback_does_not_invoke_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new EmptyCommand();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.ExecuteCommand(queryToExecute);
                op.Completed += () => syncLock.Set();
                op.Cancel();
            });
        }

        [TestMethod]
        public void query_with_empty_error_callback_does_not_invoke_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseErrorCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.Query(queryToExecute, r => { });
                op.Completed += () => syncLock.Set();
            });
        }

        [TestMethod]
        public void query_with_empty_cancel_callback_does_not_invoke_callback()
        {
            var serializer = new ResourceSerializerMock();
            var webClient = new WebClientMock().UseCompletedCallback();
            var client = new ClientContext(webClient, serializer, DefaultResourceUris);
            var queryToExecute = new QueryMock();

            TestUtil.WaitFor(syncLock =>
            {
                var op = client.Query(queryToExecute, r => { });
                op.Completed += () => syncLock.Set();
                op.Cancel();
            });
        }
    }
}